﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Inspire.Entities;
using Inspire.Classes.Common;
using Inspire.Utility;

namespace Inspire.Classes.Business
{
    /// <summary>
    /// Business class for handling database interaction related to CustomFields.
    /// </summary>
    public class CustomFieldBusiness
    {
        /// <summary>
        /// The singleton instance of CustomFieldBusiness.
        /// </summary>
        private static CustomFieldBusiness customFieldBusinessSingleton = null;

        /// <summary>
        /// Static object used for locking the GetPatronBusiness() method so no two threads can
        /// call the method simultaneously (creating two instances of this class). Synchronization
        /// is required to implement the Singleton pattern correctly.
        /// </summary>
        private static object synchLock = new object();

        /// <summary>
        /// Get a reference to our singleton context manager.
        /// </summary>
        private InspireEntities context = null;

        /// <summary>
        /// Private constructor. This is a Singleton.
        /// </summary>
        private CustomFieldBusiness()
        {
            context = ContextProvider.GetContextProvider().GetContext();
        }

        /// <summary>
        /// Method used for obtaining the Singleton instance of this class.
        /// </summary>
        /// <returns>CustomFieldBusiness</returns>
        public static CustomFieldBusiness GetCustomFieldBusiness()
        {
            lock (synchLock)
            {
                if (CustomFieldBusiness.customFieldBusinessSingleton == null)
                {
                    CustomFieldBusiness.customFieldBusinessSingleton = new CustomFieldBusiness();
                }

                return CustomFieldBusiness.customFieldBusinessSingleton;
            }
        }

        /// <summary>
        /// Returns the number of stored records that are of this CustomFieldType.
        /// </summary>
        /// <param name="customFieldType">The type of custom field type whose stored records are to be counted.</param>
        /// <returns>The number of records stored as this kind of CustomFieldType.</returns>
        public int CountRecordsOfCustomFieldType(CustomFieldType customFieldType)
        {
            // Run the query
            return (from customField in context.CustomFields
                    where customField.FieldType.Id == customFieldType.Id
                    select customField).Count();
        }

        /// <summary>
        /// Returns an IEnumberable of CustomFieldType objects for the specified category.
        /// </summary>
        /// <param name="category">Use a constant string found in the Inspire.Classes.Common.Constants class.</param>
        /// <returns>Custom Field Types for the specified category.</returns>
        public List<CustomFieldType> GetCustomFieldTypesByCategory(string category)
        {
            // Run the query
            List<CustomFieldType> customFields = (from fieldtype in context.CustomFieldTypes
                                                  where fieldtype.Category.Equals(category)
                                                  select fieldtype).ToList<CustomFieldType>();

            return customFields;
        }

        /// <summary>
        /// Find the CustomFieldType with the specified ID
        /// </summary>
        /// <param name="id">ID of the CustomFieldType to return.</param>
        /// <returns>If one, and only one, result is returned, return that. If zero or more than one result is returned, return null.</returns>
        public CustomFieldType GetCustomFieldTypeById(int id)
        {
            // Query for the object
            List<CustomFieldType> customFields = (from fieldType in context.CustomFieldTypes
                                                  where fieldType.Id == id
                                                  select fieldType).ToList<CustomFieldType>();

            // If we didn't receive exactly 1, return null
            if (customFields.Count != 1)
            {
                return null;
            }

            // Return the one and only element
            return customFields[0];
        }

        /// <summary>
        /// Returns a List of a Patron's custom fields for a certain category (contact, custom, etc.).
        /// </summary>
        /// <param name="patron">The Patron whose fields are returned.</param>
        /// <param name="category">The category of fields to return. Use the categories available in Inspire.Classes.Common.Constants.</param>
        /// <returns>Custom Fields.</returns>
        public List<CustomField> GetCustomPatronFieldsByCategory(Patron patron, string category)
        {
            // Run the query
            List<CustomField> customFields = (from customField in context.CustomFields
                                              where (customField.ForeignId == patron.Id) && (customField.FieldType.Category.Equals(category))
                                              select customField).ToList<CustomField>();

            return customFields;
        }

        /// <summary>
        /// Returns a List of a Resource's custom fields for a certain category.
        /// </summary>
        /// <param name="resource">The Resource whose fields are returned.</param>
        /// <param name="category">The category of fields to return. Use the categories available in Inspire.Classes.Common.Constants.</param>
        /// <returns>Custom Fields.</returns>
        public List<CustomField> GetCustomResourceFieldsByCategory(Resource resource, string category)
        {
            // Run the query
            List<CustomField> customFields = (from customField in context.CustomFields
                                              where (customField.ForeignId == resource.Id) && (customField.FieldType.Category.Equals(category))
                                              select customField).ToList<CustomField>();

            return customFields;
        }

        /// <summary>
        /// Saves a new Custom Field Type to the database. If successful, return true; otherwise, return false.
        /// </summary>
        /// <param name="newCustomFieldType">New Custom Field Type to be added to the database.</param>
        /// <returns>True upon success; otherwise, false.</returns>
        public bool AddCustomFieldType(CustomFieldType newCustomFieldType)
        {
            object objectFoundByKey = null;

            // If an object with this EntityKey doesn't already exist, go ahead and add this new Custom Field Type
            if((newCustomFieldType.EntityKey == null) || !(context.TryGetObjectByKey(newCustomFieldType.EntityKey, out objectFoundByKey)))
            {
                // Set the ID
                newCustomFieldType.Id = context.CustomFieldTypes.NextId(f => f.Id);

                // Add to the context
                context.CustomFieldTypes.AddObject(newCustomFieldType);
                context.SaveChanges(System.Data.Objects.SaveOptions.AcceptAllChangesAfterSave);
                return true;
            }

            // If this EntityKey is already taken, indicate an error
            return false;
        }

        /// <summary>
        /// Deletes a CustomFieldType and all of the CustomFields that are of that type.
        /// </summary>
        /// <param name="customFieldType">The CustomFieldType to be deleted.</param>
        /// <returns>Count of CustomFields deleted as a result of deleting this CustomFieldType.</returns>
        public int DeleteCustomFieldType(CustomFieldType customFieldType)
        {
            int count = 0;

            // Get the records to be deleted
            IEnumerable<CustomField> customFieldsEnum = (from customField in context.CustomFields
                                                         where customField.FieldType.Id == customFieldType.Id
                                                         select customField);

            // Mark the CustomField objects for deletion
            foreach (CustomField nextField in customFieldsEnum)
            {
                context.CustomFields.DeleteObject(nextField);
                count++;
            }

            // Mark the CustomFieldType object for deletion
            context.CustomFieldTypes.DeleteObject(customFieldType);

            // Persist the changes
            context.SaveChanges(System.Data.Objects.SaveOptions.AcceptAllChangesAfterSave);
            
            // Return the number of records deleted
            return count;
        }

        /// <summary>
        /// This method first verifies that this CustomField exists in the database. It then updates it with the new version provided.
        /// </summary>
        /// <param name="existingCustomField">CustomField object to be updated to the database.</param>
        public void SaveExistingCustomField(CustomField existingCustomField)
        {
            // Verify the CustomField exists
            context.Attach(existingCustomField);
            context.ObjectStateManager.ChangeObjectState(existingCustomField, System.Data.EntityState.Modified);
            context.SaveChanges(System.Data.Objects.SaveOptions.AcceptAllChangesAfterSave);
        }

        /// <summary>
        /// After accepting the CustomFieldType and data value of the new field, this method will create the appropriate type of CustomField and persist it to the data base.
        /// </summary>
        /// <param name="customFieldType">The CustomFieldType of the new entry.</param>
        /// <param name="data">The data value of the new CustomField.</param>
        /// <returns>The new CustomField. This will actually be one of the non-abstract sub-classes of the abstract CustomField class.</returns>
        public CustomField SaveNewCustomField(CustomFieldType customFieldType, Object data, int foreignId)
        {
            CustomField returnValue = null;

            if (customFieldType != null && customFieldType.Type.Length > 0)
            {
                // Using reflection, we are going to get the appropriate type of CustomField for the
                // CustomFieldType provided to us. Bear in mind that all Custom Field types should be named
                // according to the pattern "CustomTypeField" (e.g. "CustomBooleanField", "CustomStringField", etc.)
                try
                {
                    // Get the appropriate type of Custom Field
                    Type newType = Type.GetType("Inspire.Entities.Custom" + customFieldType.Type + "Field");

                    // Create a new field of that type
                    CustomField newFieldEntry = (CustomField)Activator.CreateInstance(newType);
                    newFieldEntry.Id = context.CustomFields.NextId(f => f.Id);
                    newFieldEntry.FieldType = (CustomFieldType)context.GetObjectByKey(customFieldType.EntityKey);
                    newFieldEntry.ForeignId = foreignId;
                    newFieldEntry.SetData(data);

                    // Save and Return the newly created field
                    context.CustomFields.AddObject(newFieldEntry);
                    context.SaveChanges(System.Data.Objects.SaveOptions.AcceptAllChangesAfterSave);

                    returnValue = newFieldEntry;
                }
                catch (Exception e)
                {
                    Console.Out.Write(e.Message);
                    return null;
                }
            }

            return returnValue;
        }

        /// <summary>
        /// Returns the number of Custom Field Types with the specified name in the specified category.
        /// </summary>
        /// <param name="category">Name of the category to search for existing custom field types in.</param>
        /// <param name="name">Name of the custom field type to search for.</param>
        /// <returns>Count of matched records.</returns>
        public CustomFieldType GetCustomFieldType(string category, string name)
        {
            try
            {
                return (from customFieldType in context.CustomFieldTypes
                        where customFieldType.Name.Equals(name) && customFieldType.Category.Equals(category)
                        select customFieldType).First();
            }
            catch (InvalidOperationException e)
            {
                return null;
            }
        }
    }
}
